<!--
 * @Descripttion: 下拉框组件增强版
 * @version: 1.0
 * @Author: tangyong
 * @Date: 2022-01-20 21:22:36
 * @LastEditors: tangyong
 * @LastEditTime: 2025-02-25 18:26:18
-->
<template>
  <div class="comp-custom-select"
    ><el-select
      v-model="selectValue"
      :placeholder="remote ? '请输入' : '请选择'"
      :loading="loading"
      :fit-input-width="false"
      loading-text="加载中..."
      remote-show-suffix
      :disabled="disabled"
      v-bind="$attrsAll"
      @visible-change="handleVisibleChange"
      @change="handleSelectChange"
      @focus="handleFocus"
    >
      <template v-if="showFilterData">
        <el-option
          v-for="(item, index) in selectOptions"
          :key="index"
          :label="item.label"
          :value="item.value"
          :disabled="item.disabled"
          ><span v-html="item.display"
        /></el-option>
      </template>
      <template v-else>
        <el-option
          v-for="(item, index) in selectOptions"
          :key="index"
          :label="item.label"
          :value="item.value"
          :disabled="item.disabled"
        />
      </template> </el-select
    ><el-icon v-if="showHyperlink && selectValue" @click="handleLinkClick"><Link /></el-icon>
  </div>
</template>

<script lang="ts" setup name="CompElSelect">
  import { isEmptyObj, isEmpty, html2Escape } from "@/utils/index";
  import { Link } from "@element-plus/icons-vue";

  const props = defineProps({
    // ajax请求参数
    params: {
      type: Object,
      default: () => ({})
    },
    // 是否获取获取焦点时请求接口
    focusRequest: {
      type: Boolean,
      default: false
    },
    // 开启接口请求
    remote: {
      type: Boolean,
      default: false
    },
    modelValue: {
      type: [String, Number, Array]
    },
    // 是否可多选
    multiple: {
      type: Boolean,
      default: false
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: false
    },
    // 字典项key
    dictNo: [String, Number],
    // 是否显示超链接按钮
    showHyperlink: {
      type: Boolean,
      defaul: false
    },
    valueToString: {
      type: Boolean,
      default: false
    }
  });

  const { proxy } = getCurrentInstance()!;
  const showFilterData = ref(false);
  const selectValue: any = ref(props.multiple ? [] : null);
  const selectOptions: any = shallowRef([]);
  const loading = ref(false);

  const $attrsAll = computed(() => {
    return {
      ...useAttrs(),
      ...{ multiple: props.multiple },
      ...(props.remote && {
        remote: true,
        "remote-method": handleFetchSuggestion
      })
    };
  });

  let controller: any = null; // 取消ajax请求对象
  const $emit = defineEmits(["update:modelValue", "change", "link-click"]);

  let updateValueFromInner = false;

  watch(
    () => {
      return props.modelValue;
    },
    (val, oldValue) => {
      if (props.multiple) {
        selectValue.value = isEmpty(val) ? [] : Array.isArray(val) ? val : (val + "").split(",");
      } else {
        selectValue.value = val;
      }

      if (updateValueFromInner) {
        updateValueFromInner = false;
        return;
      }

      if (isEmpty(val) && isEmpty(oldValue)) return;

      const newVal = Array.isArray(val) ? val.join(",") : val;
      if (props.remote) {
        if (!newVal) {
          selectOptions.value = [];
        } else {
          handleFetchSuggestion(newVal, "auto");
        }
      }
    }
  );

  watch(
    () => {
      return props.params;
    },
    (newObj, oldObj) => {
      if (typeof newObj === "string") {
        newObj = JSON.parse(newObj);
      }
      if (JSON.stringify(newObj) === JSON.stringify(oldObj)) return;
      if (isEmptyObj(newObj)) return;
      // 动态改变params, remote请求数据的情况下，options未更新
      if (newObj.clearOptions) {
        selectOptions.value = [];
      }
      if (!props.modelValue && props.remote) return;
      getDataLists();
    }
  );

  onMounted(() => {
    if (!isEmpty(props.modelValue)) {
      const val = props.modelValue;
      if (props.multiple) {
        selectValue.value = isEmpty(val) ? [] : Array.isArray(val) ? val : (val + "").split(",");
      } else {
        selectValue.value = val;
      }
    }
    if (props.dictNo) {
      const options = proxy?.$dict.queryDictItems(props.dictNo, true);
      setOptionData(options);
      return;
    }

    if (props.remote && !isEmpty(props.modelValue)) {
      handleFetchSuggestion(props.modelValue, "auto");
      return;
    }

    if (!props.remote) {
      getDataLists();
    }
  });

  function getDataLists() {
    if (props.dictNo) return;
    const { url = "", data = {}, pageSize = 100 } = props.params || {};
    if (url) {
      loading.value = true;
      controller?.abort();
      controller = new window.AbortController();
      const signal = controller.signal;
      proxy?.$http
        .post(
          url,
          {
            ...data,
            dto: {
              ...data
            },
            pageSize: pageSize,
            pageCurrent: 1
          },
          { config: { signal } }
        )
        .then((res) => {
          if (!res.data) return;
          setOptionData(res.data);
        })
        .finally(() => {
          loading.value = false;
        });
    } else {
      setOptionData();
    }
  }

  function setOptionData(arrayData: any = undefined) {
    /**
     * labelKey label取值key
     * valueKey value取值key
     * repeat 是否过滤重复数据
     * autoSelect 下拉框有值时，是否默认选择第一个
     * options 下拉框数据集合
     * dataCallback 数据预处理函数
     */
    const {
      url,
      pageSize = 100,
      labelKey = "label",
      labelKeyFn = null,
      valueKey = "value",
      repeat = true,
      autoSelect = false,
      options = [],
      dataCallback,
      autoTriggerChangeEvent = false
    } = props.params || {};
    const record = arrayData || unref(options);
    let ret = (dataCallback && dataCallback(record)) || record;
    if (!Array.isArray(ret) || !ret.length) {
      selectOptions.value = [];
      return;
    }

    if (!!url) {
      ret = ret.splice(0, pageSize);
    }

    ret = ret.filter((item) => !!item);
    if (!repeat) {
      ret = ret
        .filter((item) => !!item[labelKey])
        .reduce((prev, cur) => {
          prev.some((item) => item[labelKey] === cur[labelKey]) || prev.push(cur);
          return prev;
        }, []);
    }
    ret = ret.map((item) => {
      const label = typeof labelKeyFn === "function" ? labelKeyFn(item) + "" : item[labelKey] + "";
      return {
        label,
        value: item[valueKey] === null ? "" : item[valueKey],
        disabled: item.hasOwnProperty("disabled") ? item.disabled : false,
        data: item
      };
    });
    selectOptions.value = ret;
    if (autoSelect && ret.length && isEmpty(props.modelValue)) {
      if (ret[0].disabled) return;
      handleSelectChange(ret[0].value, "auto");
    } else if (!isEmpty(props.modelValue) && autoTriggerChangeEvent) {
      nextTick(() => {
        handleSelectChange(props.modelValue, "auto");
      });
    }
  }
  let _isFirstSearch = true;

  function handleFetchSuggestion(queryString, trigger = "manual") {
    /**
     * url 请求路径
     * data 请求参数
     * searchKey 请求参数中模糊搜索key
     * labelKey label取值key
     * valueKey value取值key
     * repeat 是否过滤重复数据
     * dataCallback 数据预处理函数
     */
    const {
      url = "",
      data = {},
      searchKey = "",
      labelKey = "label",
      labelKeyFn = null,
      valueKey = "value",
      repeat = true,
      canEmpty = false,
      pageSize = 100,
      dataCallback
    } = props.params || {};
    if (!url) return;
    let searchString = queryString;
    // 禁用联想搜索|关键字不允许为空
    if (!canEmpty && (queryString == null || !(queryString + "").trim().length)) {
      return;
    }
    selectOptions.value = [];
    loading.value = true;
    let searchObj = {};
    if (!isEmpty(searchKey)) {
      if (trigger === "manual") {
        searchObj = {
          [searchKey]: searchString
        };
      } else if (/^\d{10,}$/.test(searchString) || trigger === "auto") {
        searchObj = {
          [valueKey]: searchString
        };
      }
    }

    // 取消上一次还未完成的请求
    controller?.abort();
    controller = new window.AbortController();
    const signal = controller.signal;
    proxy?.$http
      .post(
        url,
        {
          ...data,
          ...searchObj,
          dto: {
            ...data,
            ...searchObj
          },
          pageSize: pageSize,
          pageCurrent: 1
        },
        { config: { signal } }
      )
      .then((res) => {
        if (!res.data) {
          selectOptions.value = [];
          return;
        }
        const record = res.data;
        let ret = (dataCallback && dataCallback(res.data)) || record;
        if (!Array.isArray(ret) || !ret.length) {
          selectOptions.value = [];
          return;
        }
        ret = ret.splice(0, pageSize);
        ret = ret.filter((item) => !!item);

        if (!repeat) {
          ret = ret
            .filter((item) => !!item[labelKey])
            .reduce((prev, cur) => {
              prev.some((item) => item[labelKey] === cur[labelKey]) || prev.push(cur);
              return prev;
            }, []);
        }
        ret = ret.map((item) => {
          const label =
            typeof labelKeyFn === "function" ? labelKeyFn(item) + "" : item[labelKey] + "";
          return {
            label,
            display: isEmpty(queryString)
              ? label
              : html2Escape(label + "").replace(
                  new RegExp((queryString + "").replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), "g"),
                  `<b class="red">${queryString || ""}</b>`
                ),
            value: item[valueKey],
            data: item
          };
        });
        showFilterData.value = true;
        selectOptions.value = ret;
        if (!_isFirstSearch) return;
        _isFirstSearch = false;
        if (!isEmpty(props.modelValue) && props.params.autoTriggerChangeEvent) {
          nextTick(() => {
            handleSelectChange(props.modelValue, "auto");
          });
        }
      })
      .finally(() => {
        loading.value = false;
        controller = null;
      });
  }

  function handleSelectChange(val, trigger = "manual") {
    const valArr = Array.isArray(val) ? val : [val];
    const selectedData: any = selectOptions.value.filter((item: any) =>
      valArr.includes(item.value)
    );
    $emit(
      "change",
      {
        value: val,
        data: props.multiple ? selectedData.map((item) => item.data) : selectedData[0]?.data || {}
      },
      trigger
    );
    updateValueFromInner = true;
    if (props.multiple && props.valueToString) {
      $emit("update:modelValue", Array.isArray(val) ? val.join(",") : val);
    } else {
      $emit("update:modelValue", val);
    }
  }

  function handleLinkClick() {
    $emit("link-click", selectValue.value);
  }

  function handleVisibleChange(visible) {
    if (!visible) {
      showFilterData.value = false;
    }
  }

  function handleFocus() {
    if (props.focusRequest) {
      if (isEmptyObj(props.params)) return;
      if (selectOptions.value.length) {
        return;
      }
      getDataLists();
    }
  }
</script>

<style lang="scss" scoped>
  .comp-custom-select {
    display: flex;
    align-items: center;
    .el-select {
      flex: 1;
    }
    .el-icon {
      flex-shrink: 0;
      width: 30px;
      color: var(--el-color-primary);
      cursor: pointer;
    }
  }
</style>
